package com.jzo2o.orders.manager.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.ForbiddenOperationException;
import com.jzo2o.common.utils.BeanUtils;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.dto.OrderUpdateStatusDTO;
import com.jzo2o.orders.base.service.IOrdersCommonService;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.client.CustomerClient;
import com.jzo2o.orders.manager.service.client.FoundationClient;
import com.jzo2o.orders.manager.service.client.MarketClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * <p>
 * 下单服务类
 * </p>
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {
    @Resource
    private MarketClient marketClient;
    @Resource
    private FoundationClient foundationClient;
    @Resource
    private CustomerClient customerClient;
    @Resource
    private RedisTemplate<String, Long> redisTemplate;
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private NativePayApi nativePayApi;
    @Resource
    private TradingApi tradingApi;
    @Resource
    private TradeProperties tradeProperties;
    @Resource
    private IOrdersCommonService ordersCommonService;

    /**
     * 生成订单号(2位年+2位月+2位日+13位序号)
     */
    private Long generateOrderId() {
        Long idNo = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR, 1);
        String orderId = DateUtils.format(LocalDateTime.now(), "yyMMdd") + String.format("%013d", idNo);
        return Long.valueOf(orderId);
    }

    @Override
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO placeOrderReqDTO) {
        // 获取下单的基本信息(下单人/地址等)
        AddressBookResDTO address = customerClient.getAddressDetail(placeOrderReqDTO.getAddressBookId());
        if (ObjectUtils.isEmpty(address.getId())) {
            throw new ForbiddenOperationException("无法获取地址信息下单失败");
        }
        // 获取下单的服务信息
        ServeAggregationResDTO serve = customerClient.getServeAggregation(placeOrderReqDTO.getServeId());
        if (ObjectUtils.isEmpty(serve.getId())) {
            throw new ForbiddenOperationException("无法获取服务信息下单失败");
        }
        // 获取订单id
        Long orderId = generateOrderId();
        // 计算总金额
        BigDecimal totalAmount = serve.getPrice().multiply(BigDecimal.valueOf(placeOrderReqDTO.getPurNum()));
        // 计算优惠卷相关金额
        BigDecimal discountAmount = BigDecimal.ZERO;
        Long couponId = placeOrderReqDTO.getCouponId();
        if (ObjectUtils.isNotEmpty(couponId)) {
            CouponUseResDTO couponUseResDTO = Optional
                    .ofNullable(marketClient.useCoupon(CouponUseReqDTO.builder()
                            .ordersId(orderId)
                            .id(couponId)
                            .totalAmount(totalAmount)
                            .build()))
                    .orElseThrow(() -> new ForbiddenOperationException("优惠卷核销失败下单失败"));
            discountAmount = couponUseResDTO.getDiscountAmount();
        }
        // 计算实际金额
        BigDecimal realPayAmount = totalAmount.subtract(discountAmount);

        // 组装订单信息插入数据库完成下单
        LocalDateTime serveStartTime = placeOrderReqDTO.getServeStartTime();
        Long sortColumn = DateUtils.toEpochMilli(serveStartTime) + orderId % 100000;
        Orders orderToPay = Orders.builder()
                .id(orderId)
                .userId(UserContext.currentUserId())
                .serveTypeId(serve.getServeTypeId())
                .serveTypeName(serve.getServeTypeName())
                .serveItemId(serve.getServeItemId())
                .serveItemName(serve.getServeItemName())
                .serveItemImg(serve.getServeItemImg())
                .unit(serve.getUnit())
                .serveId(serve.getId())
                .ordersStatus(OrderStatusEnum.NO_PAY.getStatus())
                .payStatus(OrderPayStatusEnum.NO_PAY.getStatus())
                .price(serve.getPrice())
                .purNum(placeOrderReqDTO.getPurNum())
                .totalAmount(totalAmount)
                .discountAmount(discountAmount)
                .realPayAmount(realPayAmount)
                .cityCode(serve.getCityCode())
                .serveAddress(address.getAddress())
                .contactsPhone(address.getPhone())
                .contactsName(address.getName())
                .serveStartTime(serveStartTime)
                .lon(address.getLon())
                .lat(address.getLat())
                .sortBy(sortColumn).build();

        // 减小事务管理的颗粒度
        return new PlaceOrderResDTO(
                transactionTemplate.execute(status -> {
                    if (baseMapper.insert(orderToPay) == 0) {
                        throw new CommonException("下单失败");
                    }
                    return orderToPay.getId();
                }));
    }

    @Override
    public OrdersPayResDTO payOrder(Long id, OrdersPayReqDTO ordersPayReqDTO) {
        Orders orders = baseMapper.selectById(id);
        if (ObjectUtils.isEmpty(orders)) {
            throw new ForbiddenOperationException("订单不存在无法支付");
        }

        // 初步防止重复发起支付请求
        if (orders.getPayStatus() == OrderPayStatusEnum.PAY_SUCCESS.getStatus()) {
            OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(orders, OrdersPayResDTO.class);
            ordersPayResDTO.setProductOrderNo(id);
            return ordersPayResDTO;
        }

        // 构造公共参数
        PayChannelEnum tradingChannel = ordersPayReqDTO.getTradingChannel();
        NativePayReqDTO nativePayReqDTO = NativePayReqDTO.builder()
                .productAppId(PRODUCT_APP_ID)
                .productOrderNo(id)
                .tradingChannel(tradingChannel)
                .tradingAmount(/*orders.getRealPayAmount() 测试统一使用0.01元*/ new BigDecimal("0.01"))
                .memo(orders.getServeItemName() + orders.getPurNum() + "个")
                .changeChannel(tradingChannel.name().equals(orders.getTradingChannel()))
                .build();

        // 获取支付渠道对应的商户号
        if (tradingChannel == PayChannelEnum.WECHAT_PAY) {
            nativePayReqDTO.setEnterpriseId(tradeProperties.getWechatEnterpriseId());
        } else if (tradingChannel == PayChannelEnum.ALI_PAY) {
            nativePayReqDTO.setEnterpriseId(tradeProperties.getAliEnterpriseId());
        } else {
            throw new ForbiddenOperationException("未知支付渠道无法支付");
        }

        // 支付服务获取支付二维码
        NativePayResDTO nativePayResDTO = nativePayApi.createDownLineTrading(nativePayReqDTO);

        // 更新订单表信息
        if (!lambdaUpdate().eq(Orders::getId, id)
                .set(Orders::getTradingOrderNo, nativePayResDTO.getTradingOrderNo())
                .set(Orders::getTradingChannel, nativePayResDTO.getTradingChannel())
                .update()) {
            log.warn("订单表更新失败(已正常生成支付二维码), 订单id: {}", id);
        }

        // 组装响应信息返回
        OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(nativePayResDTO, OrdersPayResDTO.class);
        ordersPayResDTO.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        return ordersPayResDTO;
    }

    @Override
    public OrdersPayResDTO getPayResult(Long id) {
        Orders orders = baseMapper.selectById(id);
        if (ObjectUtils.isEmpty(orders)) {
            throw new CommonException("订单不存在");
        }

        // 公共返回参数
        OrdersPayResDTO ordersPayResDTO = BeanUtils.toBean(orders, OrdersPayResDTO.class);
        ordersPayResDTO.setProductOrderNo(id);

        // 未支付订单更新状态
        Long tradingOrderNo = orders.getTradingOrderNo();
        if (ObjectUtils.isNotEmpty(tradingOrderNo)
                && orders.getPayStatus() == OrderPayStatusEnum.NO_PAY.getStatus()) {
            TradingResDTO tradingResDTO = tradingApi.findTradResultByTradingOrderNo(tradingOrderNo);

            // 支付成功
            if (ObjectUtils.isNotEmpty(tradingResDTO)
                    && tradingResDTO.getTradingState() == TradingStateEnum.YJS) {

                ordersCommonService.updateStatus(OrderUpdateStatusDTO.builder()
                        .id(id)
                        .originStatus(OrderStatusEnum.NO_PAY.getStatus())
                        .targetStatus(OrderStatusEnum.DISPATCHING.getStatus())
                        .payStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                        .tradingOrderNo(tradingResDTO.getTradingOrderNo())
                        .transactionId(tradingResDTO.getTransactionId())
                        .tradingChannel(tradingResDTO.getTradingChannel())
                        .payTime(LocalDateTime.now()).build());

                // 支付成功更新最终响应信息的状态
                ordersPayResDTO.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
            } else {
                // 未支付成功返回二维码
                ordersPayResDTO.setQrCode(tradingResDTO.getQrCode());
            }
        }

        return ordersPayResDTO;
    }

    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        ServeAggregationResDTO serve = foundationClient.getServeById(serveId);
        if (ObjectUtils.isEmpty(serve) || serve.getSaleStatus() != 2) {
            throw new BadRequestException("服务不可用");
        }

        BigDecimal totalAmount = serve.getPrice().multiply(BigDecimal.valueOf(purNum));
        return Optional
                .ofNullable(marketClient.getAvailableCoupon(totalAmount))
                .orElseGet(ArrayList::new);
    }
}